// ProcessPool.cc
#include "Task.hpp"
#include <string>
#include <vector>
#include <unistd.h>
#include <cassert>
#include <ctime>
#include <sys/stat.h>
#include <sys/wait.h>

const int processnum = 10;
std::vector<task_t> tasks;  // 任务列表

// 先描述
class channel
{
public:
    channel(int cmdfd, pid_t slaverid, const std::string &processname)
        :_cmdfd(cmdfd), _slaverid(slaverid), _processname(processname)
    {}

public:
    int _cmdfd;  // 父进程向子进程发送任务的文件描述符fd
    pid_t _slaverid;  // 子进程的PID
    std::string _processname;  // 子进程的名字，方便我们打印日志
    // int cmdcnt;  // 派发一个任务就++，子进程完成一个任务就--，然后就可以看哪个子进程闲着就给哪个子进程派任务，这样就可以做到选择进程时既不用随机数，也不用轮询
};

void slaver()
{
    // std::cout << getpid() << " - read fd is " << rfd << std::endl;
    while (true)
    {
        // sleep(1);
        int cmdcode = 0;
        int n = read(0, &cmdcode, sizeof(int));  // 如果父进程不给子进程发送数据，子进程就在这里阻塞等待
        if (n == sizeof(int))
        {
            // 执行cmdcode对应的任务列表
            std::cout << "slaver say@ get a command: " << getpid() << ", cmdcode: " << cmdcode << std::endl;
            if (cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();  // 执行命令
        }
        if (n == 0) break;
    }
}

// 函数传参规范(建议)：
// 输入型参数：&
// 输出型参数：*
// 输入输出型参数：&
void InitProcessPool(std::vector<channel> *channels)
{
    for (int i = 0; i < processnum; ++i)
    {
        int pipefd[2];  // 临时空间
        int n = pipe(pipefd);
        assert(!n);  // pipe一般不会失败
        (void)n;

        pid_t id = fork();
        if (id == 0)
        {
            // child, r
            close(pipefd[1]);
            // 把标准输入0替换成管道的读端，这样子进程去读管道数据时就直接从0读就可以了，slaver()也就不用传参了。
            dup2(pipefd[0], 0);
            close(pipefd[0]);
            slaver();
            // slaver(pipefd[0]);
            std::cout << "process: " << getpid() << " quit" << std::endl;
            exit(0);
        }

        // father, w
        close(pipefd[0]);

        // 添加channel字段
        std::string name = "process-" + std::to_string(i);
        channels->push_back(channel(pipefd[1], id, name));
    }
}

void Debug(const std::vector<channel> &channels)
{
    // test
    for (const auto &c : channels)
    {
        std::cout << c._cmdfd << " " << c._slaverid << " " << c._processname << std::endl;
    }
}

void Menu()
{
    std::cout << "########################" << std::endl;
    std::cout << "# 1.刷新日志 2.刷新野怪 #" << std::endl;
    std::cout << "# 3.检测更新 4.释放技能 #" << std::endl;
    std::cout << "# 0.退出 ###############" << std::endl;
    std::cout << "########################" << std::endl;
}

void CtrlSlaver(const std::vector<channel> &channels)
{
    int processpos = 0;  // 轮询方式选择进程
    // int cnt = 5;
    while (true)
    {
        // 1.选择任务
        // int cmdcode = rand() % tasks.size();
        Menu();
        std::cout << "Please Enter@ ";
        int select = 0;
        std::cin >> select;
        if (select == 0) break;
        else if (select < 0 && select > 4) continue;
        int cmdcode = select - 1;
        
        // 2.选择进程 - 选择进程要负载均衡，实现方式：a.随机数，b.轮询/轮转
        // int processpos = rand() % channels.size();  // 随机数方式

        std::cout << "father say# " << "cmdcode: " << cmdcode
                  << " already sendto " << channels[processpos]._slaverid
                  << " processname: " << channels[processpos]._processname << std::endl;

        // 3.发送任务
        write(channels[processpos]._cmdfd, &cmdcode, sizeof(cmdcode));

        processpos++;
        processpos %= channels.size();  // 轮询方式选择进程
        sleep(1);
    }
}

void QuitProcess(const std::vector<channel> &channels)
{
    for (const auto &c : channels)
    {
        close(c._cmdfd);  // 关闭写端文件描述符
    }
    //sleep(5);  // 观察子进程僵尸
    for (const auto &c : channels)
    {
        waitpid(c._slaverid, nullptr, 0);  // 等待子进程
    }
    //sleep(5);  // 子进程回收完毕，只剩父进程一个
}

int main()
{
    srand(time(nullptr)^getpid()^1023);  // 种一个随机数种子

    LoadTask(&tasks);  // 加载任务列表

    // 再组织
    std::vector<channel> channels;  // 通信/进程列表

    // 1.初始化
    InitProcessPool(&channels);
    Debug(channels);
    
    // 2.开始控制子进程
    CtrlSlaver(channels);

    // 3.清理收尾
    QuitProcess(channels);
    return 0;
}